/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "bctoolbox/defs.h"
#include "bctoolbox/port.h"

#include "common.h"
#include "mediastreamer2/msequalizer.h"
#include "mediastreamer2/msvolume.h"
#include <math.h>
#ifdef VIDEO_ENABLED
#include "mediastreamer2/msv4l.h"
#endif

#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#ifndef _WIN32
#include <poll.h>
#include <unistd.h>
#else
#include <malloc.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef TARGET_OS_MAC
#include <CoreFoundation/CFRunLoop.h>
#endif

#if TARGET_OS_IPHONE
#include <AudioToolbox/AudioToolbox.h>
#import <UIKit/UIKit.h>
#endif

#if TARGET_OS_IPHONE || defined(__ANDROID__)
extern void ms_set_video_stream(VideoStream *video);
#if TARGET_OS_IPHONE || defined(HAVE_X264)
extern void libmsx264_init();
#endif
#if TARGET_OS_IPHONE || defined(HAVE_OPENH264)
extern void libmsopenh264_init();
#endif
#if TARGET_OS_IPHONE || defined(HAVE_SILK)
extern void libmssilk_init();
#endif
#if TARGET_OS_IPHONE || defined(HAVE_WEBRTC)
extern void libmswebrtc_init();
#endif
#endif // TARGET_OS_IPHONE || defined (__ANDROID__)

#ifdef __ANDROID__
#include <android/log.h>
#include <jni.h>
#endif

#include "bctoolbox/crypto.h"

#ifdef HAVE_CONFIG_H
#include "mediastreamer-config.h"
#endif

#define MEDIASTREAM_MAX_ICE_CANDIDATES 3

static int cond = 1;

typedef enum _RcAlgo { RCAlgoNone, RCAlgoSimple, RCAlgoAdvanced, RCAlgoInvalid } RcAlgo;

typedef struct _MediastreamIceCandidate {
	char ip[64];
	char type[6];
	int port;
} MediastreamIceCandidate;

typedef struct _MediastreamDatas {
	MSFactory *factory;
	int localport, remoteport, payload;
	char ip[64];
	char *send_fmtp;
	char *recv_fmtp;
	int jitter;
	int bitrate;
	int mtu;
	MSVideoSize vs;
	RcAlgo rc_algo;
	bool_t ec;
	bool_t agc;
	bool_t eq;
	bool_t is_verbose;
	int device_rotation;
	VideoStream *video;
	char *capture_card;
	char *playback_card;
	char *camera;
	char *infile, *outfile;
	float ng_threshold;
	bool_t use_ng;
	bool_t two_windows;
	bool_t el;
	bool_t enable_srtp;

	bool_t interactive;
	bool_t enable_avpf;
	bool_t enable_rtcp;
	bool_t freeze_on_error;

	float el_speed;
	float el_thres;
	float el_force;
	int el_sustain;
	float el_transmit_thres;
	float ng_floorgain;
	bool_t enable_zrtp;
	PayloadType *custom_pt;
	int video_window_id;
	int preview_window_id;
	/* starting values echo canceller */
	int ec_len_ms, ec_delay_ms, ec_framesize;
	char *srtp_local_master_key;
	char *srtp_remote_master_key;
	OrtpNetworkSimulatorParams netsim;
	float zoom;
	float zoom_cx, zoom_cy;

	AudioStream *audio;
	PayloadType *pt;
	RtpSession *session;
	OrtpEvQueue *q;
	RtpProfile *profile;
	MSBandwidthController *bw_controller;

	IceSession *ice_session;
	MediastreamIceCandidate ice_local_candidates[MEDIASTREAM_MAX_ICE_CANDIDATES];
	MediastreamIceCandidate ice_remote_candidates[MEDIASTREAM_MAX_ICE_CANDIDATES];
	int ice_local_candidates_nb;
	int ice_remote_candidates_nb;
	char *video_display_filter;
	FILE *logfile;
	bool_t enable_speaker;

	bool_t enable_fec;
	RtpSession *fec_session;
	FecStream *fec_stream;
	RtpBundle *fec_bundle;
	uint8_t fec_level;
} MediastreamDatas;

// MAIN METHODS
/* init default arguments */
MediastreamDatas *init_default_args(void);
/* parse args */
bool_t parse_args(int argc, char **argv, MediastreamDatas *out);
/* setup streams */
void setup_media_streams(MediastreamDatas *args);
/* run loop*/
void mediastream_run_loop(MediastreamDatas *args);
/* exit */
void clear_mediastreams(MediastreamDatas *args);

// HELPER METHODS
void stop_handler(int signum);
static bool_t parse_addr(const char *addr, char *ip, size_t len, int *port);
static bool_t parse_ice_addr(char *addr, char *type, size_t type_len, char *ip, size_t ip_len, int *port);
static void display_items(void *user_data, uint32_t csrc, rtcp_sdes_type_t t, const char *content, uint8_t content_len);
static void parse_rtcp(mblk_t *m);
static void parse_events(RtpSession *session, OrtpEvQueue *q);
static bool_t parse_window_ids(const char *ids, int *video_id, int *preview_id);
static RcAlgo parse_rc_algo(const char *algo);

const char *usage =
    "mediastream --local <port>\n"
    "--remote <ip:port> \n"
    "[--help (display this help) ]\n"
    "[--payload <payload type number or payload name like 'audio/pmcu/8000'> ]\n"
    "[ --agc (enable automatic gain control) ]\n"
    "[ --bitrate <bits per seconds> ]\n"
    "[ --camera <camera id as listed at startup> ]\n"
    "[ --capture-card <name> ]\n"
    "[ --ec (enable echo canceller) ]\n"
    "[ --ec-delay <echo canceller delay in ms> ]\n"
    "[ --ec-framesize <echo canceller framesize in samples> ]\n"
    "[ --ec-tail <echo canceller tail length in ms> ]\n"
    "[ --el (enable echo limiter) ]\n"
    "[ --el-force <(float) [0-1]> (The proportional coefficient controlling the mic attenuation) ]\n"
    "[ --el-speed <(float) [0-1]> (gain changes are smoothed with a coefficent) ]\n"
    "[ --el-sustain <(int)> (Time in milliseconds for which the attenuation is kept unchanged after) ]\n"
    "[ --el-thres <(float) [0-1]> (Threshold above which the system becomes active) ]\n"
    "[ --el-transmit-thres <(float) [0-1]> (TO BE DOCUMENTED) ]\n"
    "[ --fmtp <fmtpline> ]\n"
    "[ --recv_fmtp <fmtpline passed to decoder> ]\n"
    "[ --freeze-on-error (for video, stop upon decoding error until next valid frame) ]\n"
    "[ --height <pixels> ]\n"
    "[ --ice-local-candidate <ip:port:[host|srflx|prflx|relay]> ]\n"
    "[ --ice-remote-candidate <ip:port:[host|srflx|prflx|relay]> ]\n"
    "[ --infile <input wav file> specify a wav file to be used for input, instead of soundcard ]\n"
    "[ --interactive (run in interactive mode) ]\n"
    "[ --jitter <miliseconds> ]\n"
    "[ --log <file> ]\n"
    "[ --mtu <mtu> (specify MTU)]\n"
    "[ --netsim-bandwidth <bandwidth limit in bits/s> (simulates a network download bandwidth limit) ]\n"
    "[ --netsim-consecutive-loss-probability <0-1> (to simulate bursts of lost packets) ]\n"
    "[ --netsim-jitter-burst-density <0-10> (density of gap/burst events, 1.0=one gap/burst per second in average) ]\n"
    "[ --netsim-jitter-strength <0-100> (strength of the jitter simulation) ]\n"
    "[ --netsim-latency <latency in ms> (simulates a network latency) ]\n"
    "[ --netsim-lossrate <0-100> (simulates a network lost rate) ]\n"
    "[ --netsim-mode inbound|outboud (whether network simulation is applied to incoming (default) or outgoing stream) "
    "]\n"
    "[ --ng (enable noise gate)] \n"
    "[ --ng-floorgain <(float) [0-1]> (gain applied to the signal when its energy is below the threshold.) ]\n"
    "[ --ng-threshold <(float) [0-1]> (noise gate threshold) ]\n"
    "[ --no-avpf ]\n"
    "[ --no-rtcp ]\n"
    "[ --outfile <output wav file> specify a wav file to write audio into, instead of soundcard ]\n"
    "[ --playback-card <name> ]\n"
    "[ --rc <rate control algorithm> possible values are: none, simple, advanced ]\n"
    "[ --srtp <local master_key> <remote master_key> (enable srtp, master key is generated if absent from comand line) "
    "]\n"
    "[ --verbose (most verbose messages) ]\n"
    "[ --video-display-filter <name> ]\n"
    "[ --video-windows-id <video surface:preview surface >]\n"
    "[ --width <pixels> ]\n"
    "[ --zoom zoom factor ]\n"
    "[ --zrtp (enable zrtp) ]\n"
    "[ --fec <level> possible values are: 0 (fec enabled but nothing sent), 1 (1D, L=10), 2 (1D interleaved, L=5, "
    "D=5), 3 (2D, L=5, D=5), 4 (2D, L=4, D=4) and 5 (2D, L=3, D=3) ]\n"
#if TARGET_OS_IPHONE
    "[ --speaker route audio to speaker ]\n"
#endif
    ;

#if TARGET_OS_IPHONE
int g_argc;
char **g_argv;
static int _main(int argc, char *argv[]);

static void *apple_main(void *data) {
	_main(g_argc, g_argv);
	return NULL;
}
int main(int argc, char *argv[]) {
	pthread_t main_thread;
	g_argc = argc;
	g_argv = argv;
	pthread_create(&main_thread, NULL, apple_main, NULL);
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	int value = UIApplicationMain(0, nil, nil, nil);
	[pool release];
	return value;
	cond = 0;
	pthread_join(main_thread, NULL);
	return 0;
}
static int _main(int argc, char *argv[])
#endif

#if !TARGET_OS_MAC && !defined(__ANDROID__)
    int main(int argc, char *argv[])
#endif

#if !defined(__ANDROID__) && !TARGET_OS_MAC || TARGET_OS_IPHONE
{
	MediastreamDatas *args;
	cond = 1;

	args = init_default_args();

	if (!parse_args(argc, argv, args)) {
		printf("%s", usage);
		return 0;
	}

	setup_media_streams(args);

	mediastream_run_loop(args);

	clear_mediastreams(args);

	free(args);

	return 0;
}

#endif

MediastreamDatas *init_default_args(void) {
	MediastreamDatas *args = (MediastreamDatas *)ms_malloc0(sizeof(MediastreamDatas));
	args->localport = 0;
	args->remoteport = 0;
	args->payload = 0;
	memset(args->ip, 0, sizeof(args->ip));
	args->send_fmtp = NULL;
	args->recv_fmtp = NULL;
	args->jitter = 50;
	args->bitrate = 0;
	args->ec = FALSE;
	args->agc = FALSE;
	args->eq = FALSE;
	args->interactive = FALSE;
	args->is_verbose = FALSE;
	args->device_rotation = -1;
	args->rc_algo = RCAlgoNone;

#ifdef VIDEO_ENABLED
	args->video = NULL;
#endif
	args->capture_card = NULL;
	args->playback_card = NULL;
	args->camera = NULL;
	args->infile = args->outfile = NULL;
	args->ng_threshold = -1;
	args->use_ng = FALSE;
	args->two_windows = FALSE;
	args->el = FALSE;
	args->el_speed = -1;
	args->el_thres = -1;
	args->el_force = -1;
	args->el_sustain = -1;
	args->el_transmit_thres = -1;
	args->ng_floorgain = -1;
	args->enable_zrtp = FALSE;
	args->custom_pt = NULL;
	args->video_window_id = -1;
	args->preview_window_id = -1;
	args->enable_avpf = TRUE;
	args->enable_rtcp = TRUE;
	/* starting values echo canceller */
	args->ec_len_ms = args->ec_delay_ms = args->ec_framesize = 0;
	args->enable_srtp = FALSE;
	args->srtp_local_master_key = args->srtp_remote_master_key = NULL;
	args->zoom = 1.0;
	args->zoom_cx = args->zoom_cy = 0.5;

	args->audio = NULL;
	args->session = NULL;
	args->pt = NULL;
	args->q = NULL;
	args->profile = NULL;
	args->logfile = NULL;

	args->ice_session = NULL;
	memset(args->ice_local_candidates, 0, sizeof(args->ice_local_candidates));
	memset(args->ice_remote_candidates, 0, sizeof(args->ice_remote_candidates));
	args->ice_local_candidates_nb = args->ice_remote_candidates_nb = 0;
	args->video_display_filter = NULL;

	args->enable_fec = FALSE;

	return args;
}

bool_t parse_args(int argc, char **argv, MediastreamDatas *out) {
	int i;

	if (argc < 4) {
		ms_error("Expected at least 3 arguments.\n");
		return FALSE;
	}

	/* default size */
	out->vs.width = MS_VIDEO_SIZE_CIF_W;
	out->vs.height = MS_VIDEO_SIZE_CIF_H;

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
			return FALSE;
		} else if (strcmp(argv[i], "--local") == 0) {
			char *is_invalid;
			i++;
			out->localport = strtol(argv[i], &is_invalid, 10);
			if (*is_invalid != '\0') {
				ms_error("Failed to parse local port '%s'\n", argv[i]);
				return 0;
			}
		} else if (strcmp(argv[i], "--remote") == 0) {
			i++;
			if (!parse_addr(argv[i], out->ip, sizeof(out->ip), &out->remoteport)) {
				ms_error("Failed to parse remote address '%s'\n", argv[i]);
				return FALSE;
			}
			ms_message("Remote addr: ip=%s port=%i\n", out->ip, out->remoteport);
		} else if (strcmp(argv[i], "--ice-local-candidate") == 0) {
			MediastreamIceCandidate *candidate;
			i++;
			if (out->ice_local_candidates_nb >= MEDIASTREAM_MAX_ICE_CANDIDATES) {
				ms_warning("Ignore ICE local candidate \"%s\" (maximum %d candidates allowed)\n", argv[i],
				           MEDIASTREAM_MAX_ICE_CANDIDATES);
				continue;
			}
			candidate = &out->ice_local_candidates[out->ice_local_candidates_nb];
			if (!parse_ice_addr(argv[i], candidate->type, sizeof(candidate->type), candidate->ip, sizeof(candidate->ip),
			                    &candidate->port)) {
				ms_error("Failed to parse ICE local candidates '%s'\n", argv[i]);
				return FALSE;
			}
			out->ice_local_candidates_nb++;
			ms_message("ICE local candidate: type=%s ip=%s port=%i\n", candidate->type, candidate->ip, candidate->port);
		} else if (strcmp(argv[i], "--ice-remote-candidate") == 0) {
			MediastreamIceCandidate *candidate;
			i++;
			if (out->ice_remote_candidates_nb >= MEDIASTREAM_MAX_ICE_CANDIDATES) {
				ms_warning("Ignore ICE remote candidate \"%s\" (maximum %d candidates allowed)\n", argv[i],
				           MEDIASTREAM_MAX_ICE_CANDIDATES);
				continue;
			}
			candidate = &out->ice_remote_candidates[out->ice_remote_candidates_nb];
			if (!parse_ice_addr(argv[i], candidate->type, sizeof(candidate->type), candidate->ip, sizeof(candidate->ip),
			                    &candidate->port)) {
				ms_error("Failed to parse ICE remote candidates '%s'\n", argv[i]);
				return FALSE;
			}
			out->ice_remote_candidates_nb++;
			ms_message("ICE remote candidate: type=%s ip=%s port=%i\n", candidate->type, candidate->ip,
			           candidate->port);
		} else if (strcmp(argv[i], "--payload") == 0) {
			i++;
			if (isdigit(argv[i][0])) {
				out->payload = atoi(argv[i]);
			} else {
				out->payload = 114;
				out->custom_pt = ms_tools_parse_custom_payload(argv[i]);
			}
		} else if (strcmp(argv[i], "--fmtp") == 0) {
			i++;
			out->send_fmtp = argv[i];
		} else if (strcmp(argv[i], "--recv_fmtp") == 0) {
			i++;
			out->recv_fmtp = argv[i];
		} else if (strcmp(argv[i], "--jitter") == 0) {
			i++;
			out->jitter = atoi(argv[i]);
		} else if (strcmp(argv[i], "--bitrate") == 0) {
			i++;
			out->bitrate = atoi(argv[i]);
		} else if (strcmp(argv[i], "--width") == 0) {
			i++;
			out->vs.width = atoi(argv[i]);
		} else if (strcmp(argv[i], "--height") == 0) {
			i++;
			out->vs.height = atoi(argv[i]);
		} else if (strcmp(argv[i], "--capture-card") == 0) {
			i++;
			out->capture_card = argv[i];
		} else if (strcmp(argv[i], "--playback-card") == 0) {
			i++;
			out->playback_card = argv[i];
		} else if (strcmp(argv[i], "--ec") == 0) {
			out->ec = TRUE;
		} else if (strcmp(argv[i], "--ec-tail") == 0) {
			i++;
			out->ec_len_ms = atoi(argv[i]);
		} else if (strcmp(argv[i], "--ec-delay") == 0) {
			i++;
			out->ec_delay_ms = atoi(argv[i]);
		} else if (strcmp(argv[i], "--ec-framesize") == 0) {
			i++;
			out->ec_framesize = atoi(argv[i]);
		} else if (strcmp(argv[i], "--agc") == 0) {
			out->agc = TRUE;
		} else if (strcmp(argv[i], "--eq") == 0) {
			out->eq = TRUE;
		} else if (strcmp(argv[i], "--ng") == 0) {
			out->use_ng = 1;
		} else if (strcmp(argv[i], "--rc") == 0) {
			i++;
			out->rc_algo = parse_rc_algo(argv[i]);
			if (out->rc_algo == RCAlgoInvalid) {
				ms_error("Invalid argument for --rc");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--ng-threshold") == 0) {
			i++;
			out->ng_threshold = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--ng-floorgain") == 0) {
			i++;
			out->ng_floorgain = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--two-windows") == 0) {
			out->two_windows = TRUE;
		} else if (strcmp(argv[i], "--infile") == 0) {
			i++;
			out->infile = argv[i];
		} else if (strcmp(argv[i], "--outfile") == 0) {
			i++;
			out->outfile = argv[i];
		} else if (strcmp(argv[i], "--camera") == 0) {
			i++;
			out->camera = argv[i];
		} else if (strcmp(argv[i], "--el") == 0) {
			out->el = TRUE;
		} else if (strcmp(argv[i], "--el-speed") == 0) {
			i++;
			out->el_speed = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--el-thres") == 0) {
			i++;
			out->el_thres = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--el-force") == 0) {
			i++;
			out->el_force = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--el-sustain") == 0) {
			i++;
			out->el_sustain = atoi(argv[i]);
		} else if (strcmp(argv[i], "--el-transmit-thres") == 0) {
			i++;
			out->el_transmit_thres = (float)atof(argv[i]);
		} else if (strcmp(argv[i], "--zrtp") == 0) {
			out->enable_zrtp = TRUE;
		} else if (strcmp(argv[i], "--verbose") == 0) {
			out->is_verbose = TRUE;
		} else if (strcmp(argv[i], "--video-windows-id") == 0) {
			i++;
			if (!parse_window_ids(argv[i], &out->video_window_id, &out->preview_window_id)) {
				ms_error("Failed to parse window ids '%s'\n", argv[i]);
				return FALSE;
			}
		} else if (strcmp(argv[i], "--device-rotation") == 0) {
			i++;
			out->device_rotation = atoi(argv[i]);
		} else if (strcmp(argv[i], "--srtp") == 0) {
			if (!ms_srtp_supported()) {
				ms_error("srtp support not enabled");
				return FALSE;
			}
			out->enable_srtp = TRUE;
			i++;
			// check if we're being given keys
			if (i + 1 < argc) {
				out->srtp_local_master_key = argv[i++];
				out->srtp_remote_master_key = argv[i++];
			}
		} else if (strcmp(argv[i], "--netsim-bandwidth") == 0) {
			i++;
			if (i < argc) {
				out->netsim.max_bandwidth = (float)atoi(argv[i]);
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-bandwidth");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-lossrate") == 0) {
			i++;
			if (i < argc) {
				out->netsim.loss_rate = (float)atoi(argv[i]);
				if (out->netsim.loss_rate < 0 || out->netsim.loss_rate > 100) {
					ms_error("Loss rate must be between 0 and 100.");
					return FALSE;
				}
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-lossrate");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-consecutive-loss-probability") == 0) {
			i++;
			if (i < argc) {
				sscanf(argv[i], "%f", &out->netsim.consecutive_loss_probability);
				if (out->netsim.consecutive_loss_probability < 0 || out->netsim.consecutive_loss_probability > 1) {
					ms_error("The consecutive loss probability must be between 0 and 1.");
					return FALSE;
				}

				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-consecutive-loss-probability");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-latency") == 0) {
			i++;
			if (i < argc) {
				out->netsim.latency = atoi(argv[i]);
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-latency");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-jitter-burst-density") == 0) {
			i++;
			if (i < argc) {
				sscanf(argv[i], "%f", &out->netsim.jitter_burst_density);
				if (out->netsim.jitter_burst_density < 0 || out->netsim.jitter_burst_density > 10) {
					ms_error("The jitter burst density must be between 0 and 10");
					return FALSE;
				}
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-jitter-burst-density");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-jitter-strength") == 0) {
			i++;
			if (i < argc) {
				sscanf(argv[i], "%f", &out->netsim.jitter_strength);
				if (out->netsim.jitter_strength < 0 || out->netsim.jitter_strength > 100) {
					ms_error("The jitter strength must be between 0 and 100.");
					return FALSE;
				}
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-jitter-strength");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--netsim-mode") == 0) {
			i++;
			if (i < argc) {
				if (strcmp(argv[i], "inbound") == 0) out->netsim.mode = OrtpNetworkSimulatorInbound;
				else if (strcmp(argv[i], "outbound") == 0) {
					out->netsim.mode = OrtpNetworkSimulatorOutbound;
				} else {
					ms_error("Invalid value for --netsim-mode");
					return FALSE;
				}
				out->netsim.enabled = TRUE;
			} else {
				ms_error("Missing argument for --netsim-dir");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--zoom") == 0) {
			i++;
			if (sscanf(argv[i], "%f,%f,%f", &out->zoom, &out->zoom_cx, &out->zoom_cy) != 3) {
				ms_error("Invalid zoom triplet");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--mtu") == 0) {
			i++;
			if (sscanf(argv[i], "%i", &out->mtu) != 1) {
				ms_error("Invalid mtu value");
				return FALSE;
			}
		} else if (strcmp(argv[i], "--interactive") == 0) {
			out->interactive = TRUE;
		} else if (strcmp(argv[i], "--no-avpf") == 0) {
			out->enable_avpf = FALSE;
		} else if (strcmp(argv[i], "--no-rtcp") == 0) {
			out->enable_rtcp = FALSE;
		} else if (strcmp(argv[i], "--help") == 0) {
			return FALSE;
		} else if (strcmp(argv[i], "--video-display-filter") == 0) {
			i++;
			out->video_display_filter = argv[i];
		} else if (strcmp(argv[i], "--log") == 0) {
			i++;
			out->logfile = fopen(argv[i], "a+");
		} else if (strcmp(argv[i], "--freeze-on-error") == 0) {
			out->freeze_on_error = TRUE;
		} else if (strcmp(argv[i], "--speaker") == 0) {
			out->enable_speaker = TRUE;
		} else if (strcmp(argv[i], "--fec") == 0) {
			i++;
			out->enable_fec = TRUE;
			out->fec_level = (uint8_t)atoi(argv[i]);
			if (out->fec_level > 5) {
				ms_error("Invalid value for --fec");
				return FALSE;
			}
		} else {
			ms_error("Unknown option '%s'\n", argv[i]);
			return FALSE;
		}
	}
	if (out->netsim.jitter_burst_density > 0 && out->netsim.max_bandwidth == 0) {
		ms_error("Jitter probability settings requires --netsim-bandwidth to be set.");
		return FALSE;
	}
	return TRUE;
}

#ifdef VIDEO_ENABLED
static void video_stream_event_cb(void *user_pointer,
                                  BCTBX_UNUSED(const MSFilter *f),
                                  const unsigned int event_id,
                                  BCTBX_UNUSED(const void *args)) {
	MediastreamDatas *md = (MediastreamDatas *)user_pointer;
	switch (event_id) {
		case MS_VIDEO_DECODER_DECODING_ERRORS:
			ms_warning("Decoding error on videostream [%p]", md->video);
			break;
		case MS_VIDEO_DECODER_FIRST_IMAGE_DECODED:
			ms_message("First video frame decoded successfully on videostream [%p]", md->video);
			break;
	}
}
static void
video_stream_display_cb(BCTBX_UNUSED(void *user_pointer), const unsigned int event_id, BCTBX_UNUSED(const void *args)) {
	switch (event_id) {
		case MS_VIDEO_DISPLAY_ERROR_OCCURRED:
			ms_message("Video stream cannot be rendered");
			break;
	}
}
#endif

static MSSndCard *get_sound_card(MSSndCardManager *manager, const char *card_name) {
	MSSndCard *play = ms_snd_card_manager_get_card(manager, card_name);
	if (play == NULL) {
		const MSList *list = ms_snd_card_manager_get_list(manager);
		char *cards = ms_strdup("");
		while (list) {
			MSSndCard *card = (MSSndCard *)list->data;
			cards = ms_strcat_printf(cards, "- %s\n", ms_snd_card_get_string_id(card));
			list = list->next;
		}
		ms_fatal("Specified card '%s' but could not find it. Available cards are:\n%s", card_name, cards);
		ms_free(cards);
	}
	return play;
}

void mediastream_fec_enable(MediastreamDatas *args, MSFactory *factory) {
	ms_factory_set_mtu(factory, ms_factory_get_mtu(factory) - (12 + 4 * 1));
	args->fec_session = ms_create_duplex_rtp_session(ms_is_ipv6(args->ip) ? "::" : "0.0.0.0",
	                                                 rtp_session_get_local_port(args->session) + 10,
	                                                 rtp_session_get_local_rtcp_port(args->session) + 10, args->mtu);
	rtp_session_set_remote_addr(args->fec_session, args->ip, args->remoteport + 10);
	args->fec_session->fec_stream = NULL;
	FecParams *params = fec_params_new(200000);
	fec_params_update(params, args->fec_level);
	args->fec_stream = fec_stream_new(args->session, args->fec_session, params);
	args->session->fec_stream = args->fec_stream;
	if (args->netsim.enabled) {
		rtp_session_enable_network_simulation(args->fec_session, &args->netsim);
	}
	ms_message("FEC SESSION Socket number : %d", args->fec_session->rtp.gs.socket);
}

void mediastream_fec_enable_bundle(MediastreamDatas *args, BCTBX_UNUSED(MSFactory *factory)) {

	FecParams *fec_params;

	ms_message("[flexfec] set");

	args->fec_session = rtp_session_new(RTP_SESSION_SENDRECV);
	rtp_session_set_payload_type(args->fec_session, 10);
	rtp_session_set_scheduling_mode(args->fec_session, 0);
	rtp_session_set_blocking_mode(args->fec_session, 0);
	fec_params = fec_params_new(200000); // default initial state, fec is disabled

	if (args->netsim.enabled) {
		rtp_session_enable_network_simulation(args->fec_session, &args->netsim);
	}

	if (args->enable_fec) {

		args->fec_bundle = rtp_bundle_new();

		rtp_bundle_add_session(args->fec_bundle, "video_fec", args->session);
		rtp_bundle_add_session(args->fec_bundle, "video_fec", args->fec_session);
		rtp_bundle_set_primary_session(args->fec_bundle, args->session);
		args->fec_stream = fec_stream_new(args->session, args->fec_session, fec_params);
		fec_params_update(fec_params, args->fec_level);
		if (fec_stream_enabled(args->fec_stream)) {
			ms_message("[flexfec] enabled");
		} else {
			ms_message("[flexfec] disabled");
		}
	}
}
void setup_media_streams(MediastreamDatas *args) {
	/*create the rtp session */
#ifdef VIDEO_ENABLED
	MSWebCam *cam = NULL;
#endif
	MSFactory *factory;
	ortp_init();
	if (args->logfile) bctbx_set_log_file(args->logfile);

	if (args->is_verbose) {
		bctbx_set_log_level(NULL, BCTBX_LOG_DEBUG);
	} else {
		bctbx_set_log_level(NULL, BCTBX_LOG_MESSAGE);
	}

	args->factory = factory = ms_factory_new_with_voip();

#if TARGET_OS_IPHONE || defined(__ANDROID__)
#if TARGET_OS_IPHONE || (defined(HAVE_X264) && defined(VIDEO_ENABLED))
	libmsx264_init(); /*no plugin on IOS/Android */
#endif
#if TARGET_OS_IPHONE || (defined(HAVE_OPENH264) && defined(VIDEO_ENABLED))
	libmsopenh264_init(); /*no plugin on IOS/Android */
#endif
#if TARGET_OS_IPHONE || defined(HAVE_SILK)
	libmssilk_init(); /*no plugin on IOS/Android */
#endif
#if TARGET_OS_IPHONE || defined(HAVE_WEBRTC)
	libmswebrtc_init();
#endif

#endif /* TARGET_OS_IPHONE || defined(__ANDROID__) */

	rtp_profile_set_payload(&av_profile, 110, &payload_type_speex_nb);
	rtp_profile_set_payload(&av_profile, 111, &payload_type_speex_wb);
	rtp_profile_set_payload(&av_profile, 112, &payload_type_ilbc);
	rtp_profile_set_payload(&av_profile, 113, &payload_type_amr);
	rtp_profile_set_payload(&av_profile, 114, args->custom_pt);
	rtp_profile_set_payload(&av_profile, 115, &payload_type_lpc1015);
#ifdef VIDEO_ENABLED
	cam = ms_web_cam_new(ms_mire_webcam_desc_get());
	if (cam) ms_web_cam_manager_add_cam(ms_factory_get_web_cam_manager(factory), cam);
	cam = NULL;

	rtp_profile_set_payload(&av_profile, 26, &payload_type_jpeg);
	rtp_profile_set_payload(&av_profile, 98, &payload_type_h263_1998);
	rtp_profile_set_payload(&av_profile, 97, &payload_type_theora);
	rtp_profile_set_payload(&av_profile, 99, &payload_type_mp4v);
	rtp_profile_set_payload(&av_profile, 100, &payload_type_x_snow);
	rtp_profile_set_payload(&av_profile, 102, &payload_type_h264);
	rtp_profile_set_payload(&av_profile, 103, &payload_type_vp8);

	args->video = NULL;
#endif
	args->profile = rtp_profile_clone_full(&av_profile);
	args->q = ortp_ev_queue_new();

	if (args->rc_algo == RCAlgoAdvanced) {
		args->bw_controller = ms_bandwidth_controller_new();
	}

	if (args->mtu) ms_factory_set_mtu(factory, args->mtu);
	ms_factory_enable_statistics(factory, TRUE);
	ms_factory_reset_statistics(factory);

	args->ice_session = ice_session_new();
	ice_session_set_remote_credentials(args->ice_session, "1234", "1234567890abcdef123456");
	// ICE local credentials are assigned when creating the ICE session, but force them here to simplify testing
	ice_session_set_local_credentials(args->ice_session, "1234", "1234567890abcdef123456");
	ice_dump_session(args->ice_session);

	signal(SIGINT, stop_handler);
	args->pt = rtp_profile_get_payload(args->profile, args->payload);
	if (args->pt == NULL) {
		ms_error("No payload defined with number %i.\n", args->payload);
		exit(-1);
	}
	if (args->enable_avpf == TRUE) {
		PayloadTypeAvpfParams avpf_params;
		payload_type_set_flag(args->pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED);
		avpf_params.features =
		    PAYLOAD_TYPE_AVPF_FIR | PAYLOAD_TYPE_AVPF_PLI | PAYLOAD_TYPE_AVPF_SLI | PAYLOAD_TYPE_AVPF_RPSI;
		avpf_params.trr_interval = 3000;
		payload_type_set_avpf_params(args->pt, avpf_params);
	} else {
		payload_type_unset_flag(args->pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED);
	}
	if (args->send_fmtp != NULL) payload_type_set_send_fmtp(args->pt, args->send_fmtp);
	if (args->recv_fmtp != NULL) payload_type_set_recv_fmtp(args->pt, args->recv_fmtp);
	if (args->bitrate > 0) args->pt->normal_bitrate = args->bitrate;

	if (args->pt->normal_bitrate == 0) {
		ms_error("Default bitrate specified for codec %s/%i. "
		         "Please specify a network bitrate with --bitrate option.\n",
		         args->pt->mime_type, args->pt->clock_rate);
		exit(-1);
	}

	// do we need to generate srtp keys ?
	if (args->enable_srtp) {
		// default profile require key-length = 30 bytes
		//  -> input : 40 b64 encoded bytes
		if (!args->srtp_local_master_key) {
			unsigned char tmp[30];
			size_t key_len = 40;
			bctbx_random_bytes(tmp, sizeof(tmp));
			args->srtp_local_master_key = (char *)malloc(41);
			bctbx_base64_encode((unsigned char *)args->srtp_local_master_key, &key_len, (unsigned char *)tmp, 30);
			args->srtp_local_master_key[key_len] = '\0';
			ms_message("Generated local srtp key: '%s'", args->srtp_local_master_key);
		}
		if (!args->srtp_remote_master_key) {
			unsigned char tmp[30];
			size_t key_len = 40;
			bctbx_random_bytes(tmp, sizeof(tmp));
			args->srtp_remote_master_key = (char *)malloc(41);
			bctbx_base64_encode((unsigned char *)args->srtp_remote_master_key, &key_len, (unsigned char *)tmp, 30);
			args->srtp_remote_master_key[key_len] = '\0';
			ms_message("Generated remote srtp key: '%s'", args->srtp_remote_master_key);
		}
	}

	if (args->pt->type != PAYLOAD_VIDEO) {
		MSSndCardManager *manager = ms_factory_get_snd_card_manager(factory);
		MSSndCard *capt = args->capture_card == NULL ? ms_snd_card_manager_get_default_capture_card(manager)
		                                             : get_sound_card(manager, args->capture_card);
		MSSndCard *play = args->playback_card == NULL ? ms_snd_card_manager_get_default_capture_card(manager)
		                                              : get_sound_card(manager, args->playback_card);
		args->audio = audio_stream_new(factory, args->localport, args->localport + 1, ms_is_ipv6(args->ip));
		if (args->bw_controller) {
			ms_bandwidth_controller_add_stream(args->bw_controller, (MediaStream *)args->audio);
		}
		audio_stream_enable_automatic_gain_control(args->audio, args->agc);
		audio_stream_enable_noise_gate(args->audio, args->use_ng);
		audio_stream_set_echo_canceller_params(args->audio, args->ec_len_ms, args->ec_delay_ms, args->ec_framesize);
		audio_stream_enable_echo_limiter(args->audio, args->el);
		audio_stream_enable_adaptive_bitrate_control(args->audio, args->rc_algo == RCAlgoSimple);
		if (capt)
			ms_snd_card_set_preferred_sample_rate(capt,
			                                      rtp_profile_get_payload(args->profile, args->payload)->clock_rate);
		if (play)
			ms_snd_card_set_preferred_sample_rate(play,
			                                      rtp_profile_get_payload(args->profile, args->payload)->clock_rate);
		ms_message("Starting audio stream.\n");

		audio_stream_start_full(args->audio, args->profile, args->ip, args->remoteport, args->ip,
		                        args->enable_rtcp ? args->remoteport + 1 : -1, args->payload, args->jitter,
		                        args->infile, args->outfile, args->outfile == NULL ? play : NULL,
		                        args->infile == NULL ? capt : NULL, args->infile != NULL ? FALSE : args->ec);

		if (args->ice_local_candidates_nb || args->ice_remote_candidates_nb) {
			args->audio->ms.ice_check_list = ice_check_list_new();
			rtp_session_set_pktinfo(args->audio->ms.sessions.rtp_session, TRUE);
			ice_session_add_check_list(args->ice_session, args->audio->ms.ice_check_list, 0);
		}
		if (args->ice_local_candidates_nb) {
			MediastreamIceCandidate *candidate;
			int c;
			for (c = 0; c < args->ice_local_candidates_nb; c++) {
				candidate = &args->ice_local_candidates[c];
				ice_add_local_candidate(args->audio->ms.ice_check_list, candidate->type, AF_INET, candidate->ip,
				                        candidate->port, 1, NULL);
				ice_add_local_candidate(args->audio->ms.ice_check_list, candidate->type, AF_INET, candidate->ip,
				                        candidate->port + 1, 2, NULL);
			}
		}
		if (args->ice_remote_candidates_nb) {
			char foundation[4];
			MediastreamIceCandidate *candidate;
			int c;
			for (c = 0; c < args->ice_remote_candidates_nb; c++) {
				candidate = &args->ice_remote_candidates[c];
				memset(foundation, '\0', sizeof(foundation));
				snprintf(foundation, sizeof(foundation) - 1, "%u", c + 1);
				ice_add_remote_candidate(args->audio->ms.ice_check_list, candidate->type, AF_INET, candidate->ip,
				                         candidate->port, 1, 0, foundation, FALSE);
				ice_add_remote_candidate(args->audio->ms.ice_check_list, candidate->type, AF_INET, candidate->ip,
				                         candidate->port + 1, 2, 0, foundation, FALSE);
			}
		}

		if (args->audio) {
			if (args->el) {
				if (args->el_speed != -1)
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_EA_SPEED, &args->el_speed);
				if (args->el_force != -1)
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_EA_FORCE, &args->el_force);
				if (args->el_thres != -1)
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_EA_THRESHOLD, &args->el_thres);
				if (args->el_sustain != -1)
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_EA_SUSTAIN, &args->el_sustain);
				if (args->el_transmit_thres != -1)
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_EA_TRANSMIT_THRESHOLD,
					                      &args->el_transmit_thres);
			}
			if (args->use_ng) {
				if (args->ng_threshold != -1) {
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_NOISE_GATE_THRESHOLD,
					                      &args->ng_threshold);
					ms_filter_call_method(args->audio->volrecv, MS_VOLUME_SET_NOISE_GATE_THRESHOLD,
					                      &args->ng_threshold);
				}
				if (args->ng_floorgain != -1) {
					ms_filter_call_method(args->audio->volsend, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN,
					                      &args->ng_floorgain);
					ms_filter_call_method(args->audio->volrecv, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN,
					                      &args->ng_floorgain);
				}
			}

			if (args->enable_zrtp) {
				MSZrtpParams params = {0};
				audio_stream_enable_zrtp(args->audio, &params);
			}

			args->session = args->audio->ms.sessions.rtp_session;
		}

		if (args->enable_srtp) {
			ms_message("SRTP enabled: %d",
			           audio_stream_enable_srtp(args->audio, MS_AES_128_SHA1_80, args->srtp_local_master_key,
			                                    args->srtp_remote_master_key));
		}
#if TARGET_OS_IPHONE
		if (args->enable_speaker) {
			ms_message("Setting audio route to spaker");
			UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
			if (AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride),
			                            &audioRouteOverride) != kAudioSessionNoError) {
				ms_error("Cannot set route to speaker");
			};
		}
#endif

	} else {
#ifdef VIDEO_ENABLED
		float zoom[] = {args->zoom, args->zoom_cx, args->zoom_cy};
		MSMediaStreamIO iodef = MS_MEDIA_STREAM_IO_INITIALIZER;

		if (args->eq) {
			ms_fatal("Cannot put an audio equalizer in a video stream !");
			exit(-1);
		}
		ms_message("Starting video stream.\n");
		args->video = video_stream_new(factory, args->localport, args->localport + 1, ms_is_ipv6(args->ip));
		if (args->bw_controller) {
			ms_bandwidth_controller_add_stream(args->bw_controller, (MediaStream *)args->video);
		}
		if (args->video_display_filter) video_stream_set_display_filter_name(args->video, args->video_display_filter);

#ifdef __ANDROID__
		if (args->device_rotation >= 0) video_stream_set_device_rotation(args->video, args->device_rotation);
#endif
		video_stream_set_sent_video_size(args->video, args->vs);
		video_stream_use_preview_video_window(args->video, args->two_windows);
#if TARGET_OS_IPHONE
		NSBundle *myBundle = [NSBundle mainBundle];
		const char *nowebcam = [[myBundle pathForResource:@"nowebcamCIF" ofType:@"jpg"]
		    cStringUsingEncoding:[NSString defaultCStringEncoding]];
		ms_static_image_set_default_image(nowebcam);
		NSUInteger cpucount = [[NSProcessInfo processInfo] processorCount];
		ms_factory_set_cpu_count(args->audio->ms.factory, cpucount);
		// ms_set_cpu_count(cpucount);
#endif
		video_stream_set_event_callback(args->video, video_stream_event_cb, args);
		video_stream_set_display_callback(args->video, video_stream_display_cb, args);
		video_stream_set_freeze_on_error(args->video, args->freeze_on_error);
		video_stream_enable_adaptive_bitrate_control(args->video, args->rc_algo == RCAlgoSimple);
		if (args->camera) cam = ms_web_cam_manager_get_cam(ms_factory_get_web_cam_manager(factory), args->camera);
		if (cam == NULL) cam = ms_web_cam_manager_get_default_cam(ms_factory_get_web_cam_manager(factory));

		if (args->infile) {
			iodef.input.type = MSResourceFile;
			iodef.input.file = args->infile;
		} else {
			iodef.input.type = MSResourceCamera;
			iodef.input.camera = cam;
		}
		if (args->outfile) {
			iodef.output.type = MSResourceFile;
			iodef.output.file = args->outfile;
		} else {
			iodef.output.type = MSResourceDefault;
			iodef.output.resource_arg = NULL;
		}
		rtp_session_set_jitter_compensation(args->video->ms.sessions.rtp_session, args->jitter);
		video_stream_start_from_io(args->video, args->profile, args->ip, args->remoteport, args->ip,
		                           args->enable_rtcp ? args->remoteport + 1 : -1, args->payload, &iodef);
		args->session = args->video->ms.sessions.rtp_session;

		ms_filter_call_method(args->video->output, MS_VIDEO_DISPLAY_ZOOM, zoom);

		if (args->two_windows) {
			// Always enable mirroring for output 2
			bool_t mirroring = TRUE;
			ms_filter_call_method(args->video->output2, MS_VIDEO_DISPLAY_ENABLE_MIRRORING, &mirroring);
		}

		if (args->enable_srtp) {
			ms_message("SRTP enabled: %d",
			           video_stream_enable_strp(args->video, MS_AES_128_SHA1_80, args->srtp_local_master_key,
			                                    args->srtp_remote_master_key));
		}
		if (args->enable_zrtp) {

			MSZrtpParams params = {0};
			args->video->ms.sessions.zrtp_context = ms_zrtp_context_new(&(args->video->ms.sessions), &params);
			video_stream_start_zrtp(args->video);
		}

		if (args->enable_fec) {
			mediastream_fec_enable_bundle(args, factory);
		}
#else
		ms_error("Error: video support not compiled.\n");
#endif
	}
	ice_session_set_base_for_srflx_candidates(args->ice_session);
	ice_session_compute_candidates_foundations(args->ice_session);
	ice_session_choose_default_candidates(args->ice_session);
	ice_session_choose_default_remote_candidates(args->ice_session);
	ice_session_start_connectivity_checks(args->ice_session);

	if (args->netsim.enabled) {
		rtp_session_enable_network_simulation(args->session, &args->netsim);
	}
}

static void mediastream_tool_iterate(MediastreamDatas *args) {
#ifndef _WIN32
	struct pollfd pfd;
	int err;

	if (args->interactive) {
		pfd.fd = STDIN_FILENO;
		pfd.events = POLLIN;
		pfd.revents = 0;

		err = poll(&pfd, 1, 10);
		if (err == 1 && (pfd.revents & POLLIN)) {
			char commands[128];
			int intarg;
			commands[127] = '\0';
			ms_sleep(1); /* ensure following text be printed after ortp messages */
			if (args->eq)
				ms_message(
				    "\nPlease enter equalizer requests, such as 'eq active 1', 'eq active 0', 'eq 1200 0.1 200'\n");

			if (fgets(commands, sizeof(commands) - 1, stdin) != NULL) {
				MSEqualizerGain d = {0};
				int active;
				if (sscanf(commands, "eq active %i", &active) == 1) {
					audio_stream_enable_equalizer(args->audio, args->audio->eq_loc, active);
					ms_message("OK\n");
				} else if (sscanf(commands, "eq %f %f %f", &d.frequency, &d.gain, &d.width) == 3) {
					audio_stream_equalizer_set_gain(args->audio, args->audio->eq_loc, &d);
					ms_message("OK\n");
				} else if (sscanf(commands, "eq %f %f", &d.frequency, &d.gain) == 2) {
					audio_stream_equalizer_set_gain(args->audio, args->audio->eq_loc, &d);
					ms_message("OK\n");
				} else if (strstr(commands, "dump")) {
					int n = 0, i;
					float *t;
					MSFilter *equalizer = NULL;
					if (args->audio->eq_loc == MSEqualizerHP) {
						equalizer = args->audio->spk_equalizer;
					} else if (args->audio->eq_loc == MSEqualizerMic) {
						equalizer = args->audio->mic_equalizer;
					}
					if (equalizer) {
						ms_filter_call_method(equalizer, MS_EQUALIZER_GET_NUM_FREQUENCIES, &n);
						t = (float *)alloca(sizeof(float) * n);
						ms_filter_call_method(equalizer, MS_EQUALIZER_DUMP_STATE, t);
						for (i = 0; i < n; ++i) {
							if (fabs(t[i] - 1) > 0.01) {
								ms_message("%i:%f:0 ", (i * args->pt->clock_rate) / (2 * n), t[i]);
							}
						}
					}
					ms_message("\nOK\n");
				} else if (sscanf(commands, "lossrate %i", &intarg) == 1) {
					args->netsim.enabled = TRUE;
					args->netsim.loss_rate = intarg;
					rtp_session_enable_network_simulation(args->session, &args->netsim);
				} else if (sscanf(commands, "bandwidth %i", &intarg) == 1) {
					args->netsim.enabled = TRUE;
					args->netsim.max_bandwidth = intarg;
					rtp_session_enable_network_simulation(args->session, &args->netsim);
				} else if (strstr(commands, "quit")) {
					cond = 0;
				} else ms_warning("Cannot understand this.\n");
			}
		} else if (err == -1 && errno != EINTR) {
			ms_fatal("mediastream's poll() returned %s", strerror(errno));
		}
	} else {
		ms_usleep(10000);
	}
#else
	MSG msg;
	Sleep(10);
	while (PeekMessage(&msg, NULL, 0, 0, 1)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	/*no interactive mode on windows*/
#endif
}

void mediastream_run_loop(MediastreamDatas *args) {
	rtp_session_register_event_queue(args->session, args->q);

#if TARGET_OS_IPHONE
	if (args->video) ms_set_video_stream(args->video); /*for IOS*/
#endif

	while (cond) {
		int n;
		for (n = 0; n < 500 && cond; ++n) {
			mediastream_tool_iterate(args);
#if defined(VIDEO_ENABLED)
			if (args->video) video_stream_iterate(args->video);
#endif
			if (args->audio) audio_stream_iterate(args->audio);
		}
		if (!args->enable_fec) {
			rtp_stats_display(rtp_session_get_stats(args->session),
			                  "                     RTP STATISTICS                          ");
		} else {
			rtp_stats_display_all(rtp_session_get_stats(args->session), rtp_session_get_stats(args->fec_session),
			                      "                                RTP STATISTICS                                   ");
			fec_stream_print_stats(args->fec_stream);
		}
		if (args->session) {
			float audio_load = 0;
			float video_load = 0;

			ms_message("Bandwidth usage: download=%f kbits/sec, upload=%f kbits/sec\n",
			           rtp_session_get_recv_bandwidth(args->session) * 1e-3,
			           rtp_session_get_send_bandwidth(args->session) * 1e-3);

			if (args->audio) {
				audio_load = ms_ticker_get_average_load(args->audio->ms.sessions.ticker);
			}
#if defined(VIDEO_ENABLED)
			if (args->video) {
				video_load = ms_ticker_get_average_load(args->video->ms.sessions.ticker);
			}
#endif
			ms_message("Thread processing load: audio=%f\tvideo=%f", audio_load, video_load);
			parse_events(args->session, args->q);
			ms_message("Quality indicator : %f\n", args->audio
			                                           ? audio_stream_get_quality_rating(args->audio)
			                                           : media_stream_get_quality_rating((MediaStream *)args->video));
		}
	}
}

void clear_mediastreams(MediastreamDatas *args) {
	ms_message("stopping all...\n");
	ms_message("Average quality indicator: %f",
	           args->audio ? audio_stream_get_average_quality_rating(args->audio) : -1);

	if (args->bw_controller) {
		ms_bandwidth_controller_destroy(args->bw_controller);
	}

	if (args->audio) {
		audio_stream_stop(args->audio);
	}
#ifdef VIDEO_ENABLED
	if (args->video) {
		ms_message("Payload max size : %d", ms_factory_get_payload_max_size(args->factory));
		if (!args->enable_fec) {
			rtp_stats_display(rtp_session_get_stats(args->session),
			                  "                     RTP STATISTICS                          ");
		} else {
			rtp_stats_display_all(rtp_session_get_stats(args->session), rtp_session_get_stats(args->fec_session),
			                      "                                RTP STATISTICS                                   ");
			fec_stream_print_stats(args->fec_stream);
		}
		if (args->video->ms.ice_check_list) ice_check_list_destroy(args->video->ms.ice_check_list);
		video_stream_stop(args->video);
	}
#endif
	ms_factory_log_statistics(args->factory);
	if (args->ice_session) ice_session_destroy(args->ice_session);
	ortp_ev_queue_destroy(args->q);
	rtp_profile_destroy(args->profile);

	if (args->logfile) fclose(args->logfile);

	ms_factory_destroy(args->factory);
}

// ANDROID JNI WRAPPER
#ifdef __ANDROID__
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *ajvm, void *reserved) {
	ms_set_jvm(ajvm);

	return JNI_VERSION_1_2;
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_setVideoWindowId(JNIEnv *env,
                                                                                            jobject obj,
                                                                                            jobject id,
                                                                                            jint _args) {
#ifdef VIDEO_ENABLED
	MediastreamDatas *args = (MediastreamDatas *)_args;
	if (!args->video) return;
	video_stream_set_native_window_id(args->video, id);
#endif
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_setVideoPreviewWindowId(JNIEnv *env,
                                                                                                   jobject obj,
                                                                                                   jobject id,
                                                                                                   jint _args) {
#ifdef VIDEO_ENABLED
	MediastreamDatas *args = (MediastreamDatas *)_args;
	if (!args->video) return;
	video_stream_set_native_preview_window_id(args->video, id);
#endif
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_setDeviceRotation(JNIEnv *env,
                                                                                             jobject thiz,
                                                                                             jint rotation,
                                                                                             jint _args) {
#ifdef VIDEO_ENABLED
	MediastreamDatas *args = (MediastreamDatas *)_args;
	if (!args->video) return;
	video_stream_set_device_rotation(args->video, rotation);
#endif
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_changeCamera(JNIEnv *env,
                                                                                        jobject obj,
                                                                                        jint camId,
                                                                                        jint _args) {
#ifdef VIDEO_ENABLED
	MediastreamDatas *args = (MediastreamDatas *)_args;
	if (!args->video) return;
	char *id = (char *)malloc(15);
	snprintf(id, 15, "Android%d", camId);
	ms_message("Changing camera, trying to use: '%s'\n", id);
	video_stream_change_camera(args->video,
	                           ms_web_cam_manager_get_cam(ms_factory_get_web_cam_manager(args->video->ms.factory), id));
#endif
}

JNIEXPORT jint JNICALL Java_org_linphone_mediastream_MediastreamerActivity_stopMediaStream(JNIEnv *env, jobject obj) {
	ms_message("Requesting mediastream to stop\n");
	stop_handler(0);
	return 0;
}

JNIEXPORT jint JNICALL Java_org_linphone_mediastream_MediastreamerActivity_initDefaultArgs(JNIEnv *env, jobject obj) {
	cond = 1;
	return (unsigned int)init_default_args();
}

JNIEXPORT jboolean JNICALL Java_org_linphone_mediastream_MediastreamerActivity_parseArgs(
    JNIEnv *env, jobject obj, jint jargc, jobjectArray jargv, jint args) {
	// translate java String[] to c char*[]
	char **argv = (char **)malloc(jargc * sizeof(char *));
	int i;

	for (i = 0; i < jargc; i++) {
		jstring arg = (jstring)(*env)->GetObjectArrayElement(env, jargv, i);
		const char *str = (*env)->GetStringUTFChars(env, arg, NULL);
		if (str == NULL) argv[i] = NULL;
		else {
			argv[i] = strdup(str);
			(*env)->ReleaseStringUTFChars(env, arg, str);
		}
	}

	bool_t result = parse_args(jargc, argv, (MediastreamDatas *)args);

	for (i = 0; i < jargc; i++) {
		if (argv[i]) free(argv[i]);
	}
	return result;
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_setupMediaStreams(JNIEnv *env,
                                                                                             jobject obj,
                                                                                             jint args) {
	setup_media_streams((MediastreamDatas *)args);
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_runLoop(JNIEnv *env,
                                                                                   jobject obj,
                                                                                   jint args) {
	mediastream_run_loop((MediastreamDatas *)args);
}

JNIEXPORT void JNICALL Java_org_linphone_mediastream_MediastreamerActivity_clear(JNIEnv *env, jobject obj, jint args) {
	clear_mediastreams((MediastreamDatas *)args);
	free((MediastreamDatas *)args);
}
#endif

// HELPER METHODS
void stop_handler(BCTBX_UNUSED(int signum)) {
	cond--;
	if (cond < 0) {
		ms_error("Brutal exit (%d)\n", cond);
		exit(-1);
	}
}

static bool_t parse_addr(const char *addr, char *ip, size_t len, int *port) {
	const char *semicolon = NULL;
	size_t iplen;
	size_t slen;
	const char *p;

	*port = 0;

	for (p = addr + strlen(addr) - 1; p > addr; p--) {
		if (*p == ':') {
			semicolon = p;
			break;
		}
	}
	/*if no semicolon is present, we can assume that user provided only port*/
	if (semicolon == NULL) {
		const char *localhost = "127.0.0.1";
		char *end;
		*port = strtol(addr, &end, 10);
		if (*end != '\0' || end == addr) {
			return FALSE;
		}
		strncpy(ip, localhost, MIN(len, strlen(localhost)));
		return TRUE;
	}
	iplen = semicolon - addr;
	slen = MIN(iplen, len - 1);
	strncpy(ip, addr, slen);
	ip[slen] = '\0';
	*port = atoi(semicolon + 1);
	return TRUE;
}

static bool_t parse_ice_addr(char *addr, char *type, size_t type_len, char *ip, size_t ip_len, int *port) {
	char *semicolon = NULL;
	size_t slen;

	semicolon = strrchr(addr, ':');
	if (semicolon == NULL) return FALSE;
	slen = MIN(strlen(semicolon + 1), type_len);
	strncpy(type, semicolon + 1, type_len);
	type[slen] = '\0';
	*semicolon = '\0';
	return parse_addr(addr, ip, ip_len, port);
}

static void display_items(BCTBX_UNUSED(void *user_data),
                          BCTBX_UNUSED(uint32_t csrc),
                          rtcp_sdes_type_t t,
                          const char *content,
                          uint8_t content_len) {
	char str[256];
	int len = MIN(sizeof(str) - 1, content_len);
	strncpy(str, content, len);
	str[len] = '\0';
	switch (t) {
		case RTCP_SDES_CNAME:
			ms_message("Found CNAME=%s", str);
			break;
		case RTCP_SDES_TOOL:
			ms_message("Found TOOL=%s", str);
			break;
		case RTCP_SDES_NOTE:
			ms_message("Found NOTE=%s", str);
			break;
		default:
			ms_message("Unhandled SDES item (%s)", str);
	}
}

static void parse_rtcp(mblk_t *m) {
	RtcpParserContext rtcp_parser;
	const mblk_t *rtcp_packet = rtcp_parser_context_init(&rtcp_parser, m);
	do {
		if (rtcp_is_RR(rtcp_packet)) {
			ms_message("Receiving RTCP RR");
		} else if (rtcp_is_SR(rtcp_packet)) {
			ms_message("Receiving RTCP SR");
		} else if (rtcp_is_SDES(rtcp_packet)) {
			ms_message("Receiving RTCP SDES");
			rtcp_sdes_parse(rtcp_packet, display_items, NULL);
		} else {
			ms_message("Receiving unhandled RTCP message");
		}
	} while ((rtcp_packet = rtcp_parser_context_next_packet(&rtcp_parser)) != NULL);
	rtcp_parser_context_uninit(&rtcp_parser);
}

static void parse_events(RtpSession *session, OrtpEvQueue *q) {
	OrtpEvent *ev;

	while ((ev = ortp_ev_queue_get(q)) != NULL) {
		OrtpEventData *d = ortp_event_get_data(ev);
		switch (ortp_event_get_type(ev)) {
			case ORTP_EVENT_RTCP_PACKET_RECEIVED:
				parse_rtcp(d->packet);
				break;
			case ORTP_EVENT_RTCP_PACKET_EMITTED:
				ms_message("Jitter buffer size: %f ms", rtp_session_get_jitter_stats(session)->jitter_buffer_size_ms);
				break;
			default:
				break;
		}
		ortp_event_destroy(ev);
	}
}

static bool_t parse_window_ids(const char *ids, int *video_id, int *preview_id) {
	char *copy = strdup(ids);
	char *semicolon = strchr(copy, ':');
	if (semicolon == NULL) {
		free(copy);
		return FALSE;
	}
	*semicolon = '\0';

	*video_id = atoi(copy);
	*preview_id = atoi(semicolon + 1);
	free(copy);
	return TRUE;
}

static RcAlgo parse_rc_algo(const char *algo) {
	if (algo == NULL) return RCAlgoInvalid;
	if (strcasecmp(algo, "simple") == 0) return RCAlgoSimple;
	if (strcasecmp(algo, "advanced") == 0) return RCAlgoAdvanced;
	if (strcasecmp(algo, "none") == 0) return RCAlgoNone;
	return RCAlgoInvalid;
}
